package cn.gwpay.cloud.framework.security.config;

import cn.gwpay.cloud.framework.security.core.filter.TokenAuthenticationFilter;
import cn.gwpay.cloud.framework.web.config.WebProperties;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.checkerframework.checker.units.qual.A;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.util.*;

import static cn.gwpay.cloud.framework.common.util.collection.CollectionUtils.convertList;

@Configuration
@AutoConfigureOrder(-1)
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class GwpayWebSecurityConfigurerAdapter {


    @Resource private SecurityProperties securityProperties;
    @Resource private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource private AccessDeniedHandler accessDeniedHandler;
    @Resource private TokenAuthenticationFilter authenticationTokenFilter;
    @Resource private List<AuthorizeRequestsCustomizerInterface> authorizeRequestsCustomizers;
    @Resource private ApplicationContext applicationContext;

    @Bean
    public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 基础安全配置
        configureBaseSecurity(http);

        // 权限控制配置
        configureAuthorization(http);

        // 添加自定义过滤器
        http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    private void configureBaseSecurity(HttpSecurity http) throws Exception {
        http.cors().and()
           .csrf().disable()
           .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
           .headers().frameOptions().disable().and()
           .exceptionHandling()
               .authenticationEntryPoint(authenticationEntryPoint)
               .accessDeniedHandler(accessDeniedHandler);
    }

    private void configureAuthorization(HttpSecurity http) throws Exception {
        http.authorizeRequests(registry -> {
            // ①：全局共享规则

            //  1.1 静态资源，可匿名访问
            addStaticResourcePermissions(registry);
            // 1.2 设置 @PermitAll 无需认证
            addAnnotationBasedPermissions(registry);

            // 1.3 基于 yudao.security.permit-all-urls 无需认证
            addConfigBasedPermissions(registry);

            // ②：每个项目的自定义规则
            applyCustomConfigurers(registry);

            // ③：兜底规则，必须认证
            registry.anyRequest().authenticated();
        });
    }

    private void addStaticResourcePermissions(
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
        registry.antMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js").permitAll();
    }

    private void addAnnotationBasedPermissions(
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
        getPermitAllUrlsFromAnnotations().forEach((method, url) ->
            registry.antMatchers(method, url).permitAll()
        );
    }

    private void addConfigBasedPermissions(
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
        securityProperties.getPermitAllUrls().forEach(url ->
            registry.antMatchers(url).permitAll()
        );
    }

    private void applyCustomConfigurers(
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
        authorizeRequestsCustomizers.forEach(customizer ->
            customizer.customize(registry)
        );
    }

    // 获取注解声明权限的方法（保持原逻辑）
    private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
        Multimap<HttpMethod, String> result = HashMultimap.create();
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);

        mapping.getHandlerMethods().forEach((info, handlerMethod) -> {
            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) return;

            List<String> urls = new ArrayList<>();
            if (info.getPatternsCondition() != null) {
                urls.addAll(info.getPatternsCondition().getPatterns());
            }
            if (info.getPathPatternsCondition() != null) {
                urls.addAll(convertList(info.getPathPatternsCondition().getPatterns(),
                    p -> p.getPatternString()));
            }

            // 处理请求方法
            Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
            if (methods.isEmpty()) {
                Arrays.stream(HttpMethod.values()).forEach(m ->
                    result.putAll(m, urls)
                );
            } else {
                methods.forEach(reqMethod ->
                    result.put(convertRequestMethod(reqMethod), urls.get(0))
                );
            }
        });
        return result;
    }

    private HttpMethod convertRequestMethod(RequestMethod method) {
        return HttpMethod.valueOf(method.name());
    }
}


